package com.fr.drschizzo.raycaster.core;

import static playn.core.PlayN.assets;
import static playn.core.PlayN.graphics;
import static playn.core.PlayN.keyboard;
import java.util.ArrayList;
import java.util.Date;
import playn.core.Game;
import playn.core.Image;
import playn.core.ImageLayer;
import playn.core.Key;
import playn.core.Keyboard;
import playn.core.Keyboard.Event;
import playn.core.Net.WebSocket;
import playn.core.Surface;
import playn.core.SurfaceLayer;

public class Raycaster implements Game
{

	private static int	mapSize				= 100;
	float				fov					= 60;
	int					screenWidth			= 800;
	int					screenHeight		= 600;
	float				projectionDisance	= 0;
	float				columnAngle			= 0;
	float				mouseX				= Float.MIN_VALUE;
	float				mouseXDelta			= 0;

	Image				tileImageBlue		= assets().getImage("images/tileblue.png");
	Image				tileImageRed		= assets().getImage("images/tilered.png");
	Image				tileImageYellow		= assets().getImage("images/tileyellow.png");
	Image				tileImageGreen		= assets().getImage("images/tilegreen.png");

	ArrayList<Image>	imageArray			= new ArrayList<Image>();

	float				viewDirection		= 320;

	public class Point
	{
		float	x;
		float	y;

		public Point(float x, float y)
		{
			super();
			this.x = x;
			this.y = y;
		}

		public float getX()
		{
			return x;
		}

		public void setX(float x)
		{
			this.x = x;
		}

		public float getY()
		{
			return y;
		}

		public void setY(float y)
		{
			this.y = y;
		}

	}

	public class RayDistance
	{
		float	horizontalDistance;
		float	verticalDistance;

		Point	verticalPoint;
		Point	horizontalPoint;

		int		gridNbV;
		int		gridNbH;

	}

	// CanvasImage canvasImage = graphics().createImage(screenWidth,
	// screenHeight);
	// Canvas canvas = canvasImage.canvas();
	// ImageLayer canvasLayer = graphics().createImageLayer();
	ImageLayer				bgLayer		= graphics().createImageLayer(assets().getImage("images/bg.png"));

	SurfaceLayer			surfLayer;
	Surface					surface;

	float					wallHeight	= 512;
	float					tileSize	= 512;
	float					playerX		= 10 * tileSize + 32;
	float					playerY		= 2 * tileSize + 32;

	boolean					moveleft	= false;
	boolean					moveright	= false;
	boolean					straffleft	= false;
	boolean					straffright	= false;
	boolean					moveup		= false;
	boolean					movedown	= false;

	// byte[][] mapsmall = { { 1, 1, 1, 1, 1 }, { 1, 0, 0, 0, 1 },
	// { 1, 0, 0, 0, 1 }, { 1, 0, 0, 0, 1 }, { 1, 1, 1, 1, 1 } };

	byte[][]				map			= new byte[mapSize][mapSize];
	private final double	speed		= tileSize / 10;

	WebSocket				socket;

	@Override
	public void init()
	{
		graphics().setSize(screenWidth, screenHeight);
		surfLayer = graphics().createSurfaceLayer(screenWidth, screenHeight);
		surface = surfLayer.surface();

		imageArray.add(tileImageBlue);
		imageArray.add(tileImageGreen);
		imageArray.add(tileImageRed);
		imageArray.add(tileImageYellow);

		graphics().rootLayer().add(bgLayer);

		graphics().rootLayer().add(surfLayer);

		for (int i = 0; i < mapSize; i++)
		{
			for (int j = 0; j < mapSize; j++)
			{
				if (i == 0 || j == 0 || i == mapSize - 1 || j == mapSize - 1)
				{
					map[i][j] = 1;

				}
				else
				{
					if (RandomTools.randomBool(20))
					{
						map[i][j] = (byte) (RandomTools.randomNb(imageArray.size()) + 1);
					}
					else
						map[i][j] = 0;
				}
			}
		}

		projectionDisance = (float) ((screenWidth / 2) / Math.tan(Math.toRadians(fov / 2)));
		columnAngle = fov / screenWidth;

		keyboard().setListener(new Keyboard.Adapter()
		{
			@Override
			public void onKeyDown(Event event)
			{
				if (event.key().equals(Key.LEFT))
				{
					moveleft = true;

				}
				if (event.key().equals(Key.RIGHT))
				{
					moveright = true;

				}
				if (event.key().equals(Key.UP))
				{
					moveup = true;
				}
				if (event.key().equals(Key.DOWN))
				{
					movedown = true;
				}
				if (event.key().equals(Key.Q))
				{
					straffleft = true;
				}
				if (event.key().equals(Key.D))
				{
					straffright = true;
				}

			}

			@Override
			public void onKeyUp(Event event)
			{
				if (event.key().equals(Key.LEFT))
				{
					moveleft = false;
				}
				if (event.key().equals(Key.RIGHT))
				{
					moveright = false;
				}
				if (event.key().equals(Key.UP))
				{
					moveup = false;
				}
				if (event.key().equals(Key.DOWN))
				{
					movedown = false;
				}
				if (event.key().equals(Key.Q))
				{
					straffleft = false;
				}
				if (event.key().equals(Key.D))
				{
					straffright = false;
				}

			}

		});
	}

	@Override
	public void paint(float alpha)
	{
		// the background automatically paints itself, so no need to do anything
		// here!
	}

	boolean facingUp(float angle)
	{
		if (angle >= 0 && angle < 180)
		{
			return true;
		}
		return false;
	}

	boolean facingRight(float angle)
	{
		if (angle < 90 || angle > 270)
		{
			return true;
		}
		return false;
	}

	private int getGridCoord(float value)
	{
		int v = (int) ((value / tileSize));
		// if (v < 0)
		// return 0;
		// if (v >= mapSize)
		// return mapSize - 1;
		return v;
	}

	private float computeAngle(float angle)
	{
		if (angle >= 360)
			angle = angle - 360;
		if (angle < 0)
			angle = 360 + angle;
		return angle;
	}

	@Override
	public void update(float delta)
	{
		Date start = new Date();

		surface.clear();

		handlePlayerMovement();

		float curAngle = computeAngle(viewDirection + fov / 2);
		float betaAngle = fov / 2;
		for (int i = 0; i < screenWidth; i += 1)
		{
			RayDistance distance = computeRayDistance(curAngle);

			float correctDistance = -1;

			betaAngle = betaAngle - columnAngle;
			// System.out.println("beta : " + betaAngle);
			float f = 255 / (tileSize * mapSize);

			int imageoffset = 0;
			int curimage = 0;
			if (distance.horizontalDistance > distance.verticalDistance)
			{
				correctDistance = (float) (distance.verticalDistance * Math.cos(Math.toRadians(betaAngle)));

				imageoffset = (int) (distance.verticalPoint.getY() % tileSize);
				curimage = distance.gridNbV;

			}
			else
			{
				correctDistance = (float) (distance.horizontalDistance * Math.cos(Math.toRadians(betaAngle)));

				imageoffset = (int) (distance.horizontalPoint.getX() % tileSize);
				curimage = distance.gridNbH;

			}

			// Calcul hauteur du mur
			int computewallHeight = (int) (wallHeight / correctDistance * projectionDisance);

			int topWall = (screenHeight - computewallHeight) / 2;

			// 

			surface.drawImage(imageArray.get(curimage - 1), i, topWall, 1, computewallHeight, imageoffset, 0, 1, imageArray.get(curimage - 1).height());

//			if (i % 5 == 0)
//			{
//				surface.setFillColor(Color.argb((int) ((f * correctDistance)), 0, 0, 0));
//				surface.drawLine(i - 2.5f, topWall, i - 2.5f, topWall + computewallHeight, 5);
//			}

			curAngle = computeAngle((curAngle - (1 * columnAngle)));

		}
		Date end = new Date();
		long update = end.getTime() - start.getTime();
		// System.out.println("fps : " + 1000 / update);

	}

	private void handlePlayerMovement()
	{
		if (moveleft || mouseXDelta < 0)
			viewDirection = computeAngle(viewDirection + 6);
		if (moveright || mouseXDelta > 0)
			viewDirection = computeAngle(viewDirection - 6);
		// System.out.println(mouseXDelta);

		Point moveDirection = new Point(0, 0);
		mouseXDelta = 0;
		if (moveup)
		{
			moveDirection = computeWallCollision(viewDirection);

		}

		if (movedown)
		{
			moveDirection = computeWallCollision(viewDirection + 180);

		}

		if (straffleft)
		{
			moveDirection = computeWallCollision(viewDirection + 90);

		}
		if (straffright)
		{
			moveDirection = computeWallCollision(viewDirection - 90);

		}
		playerX = (int) (playerX + moveDirection.x);
		playerY = (int) (playerY + moveDirection.y);
	}

	private Point computeWallCollision(float angle)
	{
		Point moveDirection = computeMoveDirection(angle);
		if (getMinDistance(angle) < 100)
		{
			float tmpangle = angle;
			RayDistance distance = computeRayDistance(angle);
			if (distance.horizontalDistance > distance.verticalDistance)
			{
				tmpangle = 90;
				if (!facingUp(angle))
					tmpangle = 270;
			}
			else
			{
				tmpangle = 0;
				if (!facingRight(angle))
					tmpangle = 180;
			}
			if (getMinDistance(tmpangle) > 100)
			{
				moveDirection = computeMoveDirection(tmpangle);
				moveDirection.x = moveDirection.x * 0.2f;
				moveDirection.y = moveDirection.y * 0.2f;
			}
			else
			{
				moveDirection = new Point(0, 0);
			}

		}
		return moveDirection;
	}

	private Point computeMoveDirection(float angle)
	{
		float movex = (float) (Math.cos(Math.toRadians(angle)) * speed);
		float movey = (float) (Math.sin(Math.toRadians(angle)) * speed);
		int movexfactor = 1;
		if (facingRight(angle) && movex < 0 || !facingRight(angle) && movex > 0)
			movexfactor = -1;
		movex = movex * movexfactor;
		int moveyfactor = 1;
		if (facingUp(angle) && movey > 0 || !facingUp(angle) && movey < 0)
		{
			moveyfactor = -1;
		}
		movey = movey * moveyfactor;

		Point moveDirection = new Point(movex, movey);
		return moveDirection;
	}

	private float getMinDistance(float curAngle)
	{
		RayDistance distance = computeRayDistance(curAngle);
		if (distance.horizontalDistance < distance.verticalDistance)
			return distance.horizontalDistance;
		else
			return distance.verticalDistance;
	}

	private RayDistance computeRayDistance(float curAngle)
	{
		boolean foundVerticalWall = false;
		boolean foundHorizontalWall = false;
		RayDistance distance = new RayDistance();
		float hy = -1;
		float hx = -1;
		float vy = -1;
		float vx = -1;
		int gridx = -1;
		int gridy = -1;
		float ya = 0;
		float xa = 0;

		float hd = Float.MAX_VALUE;
		float vd = Float.MAX_VALUE;
		// detection intersections horizontales
		if (curAngle != 0 && curAngle != 180)
		{

			if (facingUp(curAngle))
			{
				hy = ((int) (playerY / tileSize)) * tileSize - 0.01f;
				ya = -tileSize;
			}
			else
			{
				hy = ((int) (playerY / tileSize)) * tileSize + tileSize;
				ya = tileSize;
			}
			hx = (float) (playerX + (playerY - hy) / Math.tan(Math.toRadians(curAngle)));
			xa = (float) (tileSize / Math.tan(Math.toRadians(curAngle)));

			float xfactor = 1;

			if ((facingRight(curAngle) && xa < 0) || !facingRight(curAngle) && xa > 0)
				xfactor = -1;
			xa = xa * xfactor;
			gridx = getGridCoord(hx);
			gridy = getGridCoord(hy);
			if (gridx >= 0 && gridx < mapSize && gridy >= 0 && gridy < mapSize)
			{
				if (map[gridx][gridy] == 0)
				{
					while (!foundHorizontalWall)
					{
						hx = hx + xa;
						hy = hy + ya;

						gridx = getGridCoord(hx);
						gridy = getGridCoord(hy);
						if (gridx >= 0 && gridx < mapSize && gridy >= 0 && gridy < mapSize)
						{
							if (map[gridx][gridy] != 0)
							{
								foundHorizontalWall = true;
								hd = (float) Math.sqrt(Math.pow(playerX - hx, 2) + Math.pow(playerY - hy, 2));
								distance.gridNbH = map[gridx][gridy];
								// hd = (float) (Math.abs(playerX - hx) /
								// Math
								// .cos(Math.toRadians(curAngle)));
							}
						}
						else
							foundHorizontalWall = true;

					}
				}
				else
				{
					foundHorizontalWall = true;
					distance.gridNbH = map[gridx][gridy];
					hd = (float) Math.sqrt(Math.pow(playerX - hx, 2) + Math.pow(playerY - hy, 2));
				}
			}
		}

		// detection intersections verticales
		if (curAngle != 90 && curAngle != 270)
		{
			if (facingRight(curAngle))
			{
				vx = ((int) (playerX / tileSize)) * tileSize + tileSize;
				xa = tileSize;
			}
			else
			{
				vx = ((int) (playerX / tileSize)) * tileSize - 0.01f;
				xa = -tileSize;
			}
			vy = (float) (playerY + (playerX - vx) * Math.tan(Math.toRadians(curAngle)));

			float yfactor = 1;
			ya = (float) (tileSize * Math.tan(Math.toRadians(curAngle)));
			if (facingUp(curAngle) && ya > 0 || !facingUp(curAngle) && ya < 0)
				yfactor = -1;
			ya = ya * yfactor;
			gridx = getGridCoord(vx);
			gridy = getGridCoord(vy);
			if (gridx >= 0 && gridx < mapSize && gridy >= 0 && gridy < mapSize)
			{
				if (map[gridx][gridy] == 0)
				{
					while (!foundVerticalWall)
					{
						vx = vx + xa;
						vy = vy + ya;

						gridx = getGridCoord(vx);
						gridy = getGridCoord(vy);
						if (gridx >= 0 && gridx < mapSize && gridy >= 0 && gridy < mapSize)
						{
							if (map[gridx][gridy] != 0)
							{
								foundVerticalWall = true;
								vd = (float) Math.sqrt(Math.pow(playerX - vx, 2) + Math.pow(playerY - vy, 2));
								distance.gridNbV = map[gridx][gridy];
							}
						}
						else
							foundVerticalWall = true;

					}
				}
				else
				{
					foundVerticalWall = true;
					distance.gridNbV = map[gridx][gridy];
					vd = (float) Math.sqrt(Math.pow(playerX - vx, 2) + Math.pow(playerY - vy, 2));
				}
			}
		}

		distance.horizontalDistance = hd;
		distance.verticalDistance = vd;
		distance.horizontalPoint = new Point(hx, hy);
		distance.verticalPoint = new Point(vx, vy);
		return distance;
	}

	@Override
	public int updateRate()
	{
		return 16;
	}
}
